package org.naxx.security.parser;

import java.util.ArrayList;
import java.util.List;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.naxx.security.datatype.DataTypeResolver;
import org.naxx.security.datatype.DataTypeValueResolver;
import org.naxx.security.model.impl.match.ValueHolderImpl;
import org.naxx.security.model.request.Action;
import org.naxx.security.model.request.Attribute;
import org.naxx.security.model.request.Environment;
import org.naxx.security.model.request.Request;
import org.naxx.security.model.request.Resource;
import org.naxx.security.model.request.Subject;
import org.naxx.security.parser.util.QNames;

/**
 * ResourceContent parsing is not yet implemented, its content is just skipped
 * at the moment.
 */
public final class RequestParser extends AbstractXmlParser<Request> {
	
	private DataTypeResolver dataTypeResolver;
	private DataTypeValueResolver dataTypeValueResolver;

	public void setDataTypeResolver(DataTypeResolver dataTypeResolver) {
		this.dataTypeResolver = dataTypeResolver;
	}
	public void setDataTypeValueResolver(DataTypeValueResolver dataTypeValueResolver) {
		this.dataTypeValueResolver = dataTypeValueResolver;
	}

	/**
	 * Precondition: cursor points to start Request element. <br>
	 * Postcondition: cursor points to closing Request element.
	 */
	public Request parse(XMLEventReader reader) throws Exception {
		Request request = new Request();
		XMLEvent event = reader.nextTag(); // now points to Subject element

		while (event.isStartElement()
				&& "Subject".equals(event.asStartElement().getName()
						.getLocalPart())) {
			Subject subject = new Subject();
			// TODO optimize this
			if (event.asStartElement().getAttributeByName(
					QNames.SUBJECT_CATEGORY_ATTRIBUTE) != null) {
				subject.setSubjectCategory(event.asStartElement()
						.getAttributeByName(QNames.SUBJECT_CATEGORY_ATTRIBUTE)
						.getValue());
			}
			subject.addAttributes(this.parseAttributes(reader));
			request.addSubject(subject);
			event = reader.nextTag();
		}
		while (event.isStartElement()
				&& "Resource".equals(event.asStartElement().getName()
						.getLocalPart())) {
			Resource resource = new Resource();

			if (reader.peek().isCharacters()) {
				event = reader.nextEvent(); // we must not call nextTag
			}
			if (!reader.peek().isStartElement()) {
				throw new IllegalStateException("Ouch bad");
			}
			// TODO ResourceContent not implemented yet, skipping it for now
			if ("ResourceContent".equals(reader.peek().asStartElement()
					.getName().getLocalPart())) {
				event = reader.nextEvent();
				loop: while (true) {
					event = reader.nextEvent();
					if (event.isEndElement()) {
						if ("ResourceContent".equals(event.asEndElement()
								.getName().getLocalPart())) {
							break loop;
						}
					}
				}
			}
			resource.addAttributes(this.parseAttributes(reader));
			request.addResource(resource);
			event = reader.nextTag();
		}

		Action action = new Action(this.parseAttributes(reader));
		event = reader.nextTag();

		Environment environment = new Environment(this.parseAttributes(reader));

		request.setAction(action);
		request.setEnvironment(environment);
		return request;
	}

	/**
	 * On entry cursor points to element just before start Attribute element,
	 * and on exit it points to the element after last closing Attribute
	 * element.
	 */
	List<Attribute> parseAttributes(XMLEventReader reader) throws Exception {
		List<Attribute> result = new ArrayList<Attribute>();
		XMLEvent event = reader.nextTag();
		while (event.isStartElement()
				&& "Attribute".equals(event.asStartElement().getName()
						.getLocalPart())) {
			StartElement start = event.asStartElement();

			Attribute attribute = new Attribute();
			attribute.setAttributeId(start.getAttributeByName(
					QNames.ATTRIBUTE_ID_ATTRIBUTE).getValue());
			attribute.setDataType(this.dataTypeResolver.resolveDataType(
							start.getAttributeByName(QNames.DATA_TYPE_ATTRIBUTE).getValue()));

			if (start.getAttributeByName(QNames.ISSUER_ATTRIBUTE) != null) {
				attribute.setIssuer(start.getAttributeByName(
						QNames.ISSUER_ATTRIBUTE).getValue());
			}
			event = reader.nextTag();
			while (event.isStartElement()
					&& "AttributeValue".equals(event.asStartElement().getName()
							.getLocalPart())) {
				// TODO for now we support only plain string content, add
				// support for LAX
				// event = reader.nextEvent();
				// String value = event.asCharacters().getData();
				StringBuilder buffer = new StringBuilder();
				event = getCharacters(reader, buffer);

				// event = reader.nextEvent(); // should point to closing
				// AttributeValue

				ValueHolderImpl valueHolder = new ValueHolderImpl();
				valueHolder.setDataType(attribute.getDataType());
				valueHolder.setValue(this.dataTypeValueResolver.resolveValue(valueHolder.getDataType(), 
						buffer.toString()));
				attribute.addValue(valueHolder);
				event = reader.nextTag();
			}
			result.add(attribute);
			event = reader.nextTag();
		}
		return result;
	}
}
